package com.mephi.oshs.model.tree;

import com.mephi.oshs.model.DelegateObservable;
import com.mephi.oshs.model.IObservable;
import com.mephi.oshs.model.Type;
import com.mephi.oshs.model.Value;
import com.mephi.oshs.model.entry.Entry;
import com.mephi.oshs.model.entry.EntryRoot;
import com.mephi.oshs.model.entry.IEntry;
import com.mephi.oshs.model.interpreter.api.IContext;
import com.mephi.oshs.model.interpreter.api.IExpression;
import com.mephi.oshs.model.style.StyleFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * Реализация дерева данных ОШС.
 * Дерево ОШС так же реализует интерфейс наблюдаемого объекта.
 * Дерево ОШС реализованно как объект-одиночка.
 *
 * @param <T> Элемент дерева ОШС.
 */
public class OshsTree<T extends IEntry> implements IObservable, ITree<T> {

    private static final Logger logger = LoggerFactory.getLogger(OshsTree.class);

    private static final OshsTree<IEntry> singleton = createBigSampleTree();

    private final DelegateObservable observable = new DelegateObservable();

    private final ITree<T> entryITree;

    private OshsTree(T head) {
        this.entryITree = new Tree<T>(head);
    }

    public static OshsTree<IEntry> getInstance() {
        return singleton;
    }

    public List<T> quickSearch(final String searchQuery) {
        final List<T> entryList = new ArrayList<T>();
        this.quickSearchRecursive(this, searchQuery, entryList);
        logger.debug("Searching entries with text: '" + searchQuery + "'. Found " + entryList.size() + " entries: " + Arrays.toString(entryList.toArray()));
        return entryList;
    }

    public List<T> getEntriesMatchingExpression(final IExpression expr, final IContext ctx, final Object key) {
        final List<T> entryList = new ArrayList<T>();
        this.getEntriesMatchingExpressionRecursive(this, expr, ctx, key, entryList);
        logger.debug("Searching entries with expression, found " + entryList.size() + " entries: " + Arrays.toString(entryList.toArray()));
        return entryList;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addLeaf(T root, T leaf) {
        delegate().addLeaf(root, leaf);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ITree<T> addLeaf(T leaf) {
        return delegate().addLeaf(leaf);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ITree<T> setAsParent(T parentRoot) {
        return delegate().setAsParent(parentRoot);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public T getHead() {
        return delegate().getHead();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ITree<T> getTree(T element) {
        return delegate().getTree(element);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ITree<T> getParent() {
        return delegate().getParent();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<T> getSuccessors(T root) {
        return delegate().getSuccessors(root);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<ITree<T>> getSubTrees() {
        return delegate().getSubTrees();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String printTree(int increment) {
        return this.delegate().printTree(increment);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean contains(T of) {
        return this.delegate().contains(of);
    }

    /* O.B.S.E.R.V.A.B.L.E. */

    @Override
    public void addObserver(Observer o) {
        this.observable().addObserver(o);
    }

    @Override
    public void deleteObserver(Observer o) {
        this.observable().deleteObserver(o);
    }

    @Override
    public void notifyObservers() {
        this.observable().notifyObservers();
    }

    @Override
    public void notifyObservers(Object arg) {
        this.observable().notifyObservers(arg);
    }

    @Override
    public void deleteObservers() {
        this.observable().deleteObservers();
    }

    @Override
    public boolean hasChanged() {
        return this.observable().hasChanged();
    }

    @Override
    public int countObservers() {
        return this.observable().countObservers();
    }

    private static OshsTree<IEntry> createSampleTree() {
        final OshsTree<IEntry> oshsTree = new OshsTree<IEntry>(new EntryRoot());
        oshsTree.addLeaf(new Entry("1"));
        Entry entry2 = new Entry("2");
        oshsTree.addLeaf(entry2);
        oshsTree.addLeaf(entry2, new Entry("2_1"));
        oshsTree.addLeaf(entry2, new Entry("2_2"));
        oshsTree.addLeaf(entry2, new Entry("2_3"));
        oshsTree.addLeaf(new Entry("3"));
        return oshsTree;
    }

    private static OshsTree<IEntry> createBigSampleTree() {
        final OshsTree<IEntry> oshsTree = new OshsTree<IEntry>(new EntryRoot());
        final int multiplexer = 4;
        ITree<IEntry> oshsTree2;
        ITree<IEntry> oshsTree3;
        ITree<IEntry> oshsTree4;
        String name;
        for (int i = 0; i < 5 * multiplexer; i++) {
            name = String.valueOf(i);
            final Map<String, Value> m = new HashMap<String, Value>();
            m.put(name, new Value(Type.STRING, "VALUE_FOR_" + name));

            oshsTree2 = oshsTree.addLeaf(new Entry(name, StyleFactory.getStyle(), m));
            for (int j = 0; j < multiplexer; j++) {
                name = i + "_" + j;
                final Map<String, Value> m2 = new HashMap<String, Value>();
                m2.putAll(m);
                m2.put(name, new Value(Type.STRING, "VALUE_FOR_" + name));

                oshsTree3 = oshsTree2.addLeaf(new Entry(name, StyleFactory.getStyle2(), m2));
                for (int k = 0; k < 4 * multiplexer; k++) {
                    name = i + "_" + j + "_" + k;
                    final Map<String, Value> m3 = new HashMap<String, Value>();
                    m3.putAll(m2);
                    m3.put(name, new Value(Type.STRING, "VALUE_FOR_" + name));

                    oshsTree4 = oshsTree3.addLeaf(new Entry(name, StyleFactory.getStyle3(), m3));
                    for (int l = 0; l < multiplexer; l++) {
                        name = i + "_" + j + "_" + k + "_" + l;
                        final Map<String, Value> m4 = new HashMap<String, Value>();
                        m4.putAll(m3);
                        m4.put(name, new Value(Type.STRING, "VALUE_FOR_" + name));

                        oshsTree4.addLeaf(new Entry(name, StyleFactory.getStyle4(), m4));
                    }
                }
            }
        }
        return oshsTree;
    }

    private ITree<T> delegate() {
        return this.entryITree;
    }

    private DelegateObservable observable() {
        return this.observable;
    }

    private void quickSearchRecursive(final ITree<T> entryTree, final String searchQuery, final List<T> entryList) {
        if (entryTree.getHead().getShortName().contains(searchQuery)) {
            logger.debug("Founded: " + entryTree.getHead().toString());
            entryList.add(entryTree.getHead());
        }
        for (final ITree<T> subEntryTree : entryTree.getSubTrees()) {
            this.quickSearchRecursive(subEntryTree, searchQuery, entryList);
        }
    }

    private void getEntriesMatchingExpressionRecursive(final ITree<T> entryTree, final IExpression expr,
                                                       final IContext ctx, final Object key, final List<T> entryList) {
        final T currentElement = entryTree.getHead();
        ctx.addVariable(key, currentElement);
        expr.interpret(ctx);
        final Object interpretResult = ctx.get(expr);
        if ((interpretResult != null) && (interpretResult.equals(Boolean.TRUE))) {
            logger.debug("Founded: " + entryTree.getHead().toString());
            entryList.add(currentElement);
        }

        // Спускаемся вниз по дереву.
        for (final ITree<T> subEntryTree : entryTree.getSubTrees()) {
            this.getEntriesMatchingExpressionRecursive(subEntryTree, expr, ctx, key, entryList);
        }
    }
}
